package com.ikingtech.platform.service.application.controller;

import com.ikingtech.framework.sdk.application.api.ApplicationApi;
import com.ikingtech.framework.sdk.application.model.ApplicationAssignDTO;
import com.ikingtech.framework.sdk.application.model.ApplicationDTO;
import com.ikingtech.framework.sdk.application.model.ApplicationQueryParamDTO;
import com.ikingtech.framework.sdk.base.model.PageParam;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.context.event.*;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.core.support.LogHelper;
import com.ikingtech.framework.sdk.enums.application.ApplicationStatusEnum;
import com.ikingtech.framework.sdk.enums.application.ApplicationTypeEnum;
import com.ikingtech.framework.sdk.enums.application.EmbeddedApplicationEnum;
import com.ikingtech.framework.sdk.enums.domain.DomainEnum;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.application.entity.ApplicationAssignDO;
import com.ikingtech.platform.service.application.entity.ApplicationDO;
import com.ikingtech.platform.service.application.exception.ApplicationExceptionInfo;
import com.ikingtech.platform.service.application.service.ApplicationAssignService;
import com.ikingtech.platform.service.application.service.ApplicationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/application", name = "应用中心-应用管理", description = "应用中心-应用管理")
public class ApplicationController implements ApplicationApi {

    private final ApplicationService service;

    private final ApplicationAssignService applicationAssignService;

    private final ApplicationContext applicationContext;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<ApplicationDTO> add(ApplicationDTO application) {
        ApplicationDO entity = Tools.Bean.copy(application, ApplicationDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setCode(Tools.Id.uuid());
        entity.setStatus(ApplicationStatusEnum.UNPUBLISHED.name());
        entity.setTenantCode(Me.tenantCode());
        this.service.save(entity);
        return R.ok(this.modelConvert(entity, false));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        this.service.removeById(id);
        return R.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(ApplicationDTO application) {
        ApplicationDO entity = this.service.getById(application.getId());
        if (null == entity) {
            throw new FrameworkException(ApplicationExceptionInfo.APPLICATION_NOT_FOUND);
        }
        ApplicationDO newEntity = Tools.Bean.copy(application, ApplicationDO.class);
        newEntity.setCode(entity.getCode());
        this.service.updateById(newEntity);
        return R.ok(this.modelConvert(newEntity, false));
    }

    @Override
    public R<List<ApplicationDTO>> page(ApplicationQueryParamDTO queryParam) {
        List<ApplicationAssignDO> applicationAssignEntities = new ArrayList<>();
        if (DomainEnum.TENANT_MANAGEMENT.name().equals(Me.domainCode()) ||
                DomainEnum.TENANT_SERVICE.name().equals(Me.domainCode())) {
            List<String> codes = this.applicationAssignService.listAppCodeByTenantCode(Me.tenantCode());
            if (Tools.Coll.isBlank(codes)) {
                return R.ok(new ArrayList<>());
            }
            queryParam.setStatus(ApplicationStatusEnum.PUBLISHED.name());
            queryParam.setTenantCode(Tools.Str.EMPTY);
            queryParam.setCodes(codes);
        } else if (DomainEnum.DEV_CENTER.name().equals(Me.domainCode())) {
            queryParam.setTenantCode(Me.tenantCode());
            queryParam.setTypes(Tools.Coll.newList(ApplicationTypeEnum.PLATFORM.name(), ApplicationTypeEnum.THIRD_PARTY.name()));
        } else {
            queryParam.setTenantCode(Me.tenantCode());
            queryParam.setStatus(ApplicationStatusEnum.PUBLISHED.name());
        }

        return R.ok(this.service.listPage(queryParam).convertBatch(entities -> this.modelConvert(entities, applicationAssignEntities)));
    }

    @Override
    public R<List<ApplicationDTO>> all() {
        return R.ok(this.modelConvert(this.service.listPublishedApplication(), DomainEnum.TENANT_MANAGEMENT.name().equals(Me.domainCode()) ? new ArrayList<>() : this.applicationAssignService.listByTenantCode(Me.tenantCode())));
    }

    @Override
    public R<ApplicationDTO> detail(String id) {
        ApplicationDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(ApplicationExceptionInfo.APPLICATION_NOT_FOUND);
        }
        ApplicationAssignDO applicationAssignEntity = null;
        if (DomainEnum.TENANT_MANAGEMENT.name().equals(Me.domainCode())) {
            applicationAssignEntity = this.applicationAssignService.getByAppCodeAndTenantCode(entity.getCode(), Me.tenantCode());
        }
        return R.ok(this.modelConvert(entity, null == applicationAssignEntity ? null : applicationAssignEntity.getEnabled()));
    }

    @Override
    public R<List<ApplicationAssignDTO>> listApplicationAssignByCurrentTenant(PageParam param) {
        PageResult<ApplicationDO> entities = this.service.pagePublishedApplication(param);
        List<String> tenantAppCodes = this.applicationAssignService.listAppCodeByTenantCode(Me.tenantCode());
        return R.ok(entities.convert(entity -> {
            ApplicationAssignDTO applicationAssign = Tools.Bean.copy(entity, ApplicationAssignDTO.class);
            applicationAssign.setAssignToCurrentTenant(tenantAppCodes.contains(entity.getCode()));
            return applicationAssign;
        }));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> publish(String id) {
        ApplicationDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(ApplicationExceptionInfo.APPLICATION_NOT_FOUND);
        }
        entity.setStatus(ApplicationStatusEnum.PUBLISHED.name());
        this.service.updateById(entity);
        if (Tools.Str.isNotBlank(entity.getTenantCode())) {
            ApplicationAssignDO assignEntity = new ApplicationAssignDO();
            assignEntity.setId(Tools.Id.uuid());
            assignEntity.setAppCode(entity.getCode());
            assignEntity.setTenantCode(Me.tenantCode());
            assignEntity.setEnabled(true);
            this.applicationAssignService.save(assignEntity);
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    applicationContext.publishEvent(new ApplicationAssignEvent(this, entity.getTenantCode(), entity.getCode()));
                    applicationContext.publishEvent(new ApplicationMenuInitEvent(this, entity.getTenantCode(), entity.getCode(), new ArrayList<>()));
                }
            });
        }
        return R.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> acquire(String id) {
        ApplicationDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(ApplicationExceptionInfo.APPLICATION_NOT_FOUND);
        }
        ApplicationAssignDO assignEntity = new ApplicationAssignDO();
        assignEntity.setId(Tools.Id.uuid());
        assignEntity.setAppCode(entity.getCode());
        assignEntity.setTenantCode(Me.tenantCode());
        assignEntity.setEnabled(true);
        this.applicationAssignService.save(assignEntity);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                applicationContext.publishEvent(new ApplicationAssignEvent(this, Me.tenantCode(), entity.getCode()));
                applicationContext.publishEvent(new ApplicationMenuInitEvent(this, Me.tenantCode(), entity.getCode(), new ArrayList<>()));
            }
        });
        return R.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> remove(String id) {
        ApplicationDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(ApplicationExceptionInfo.APPLICATION_NOT_FOUND);
        }
        if (DomainEnum.TENANT_MANAGEMENT.name().equals(Me.domainCode())) {
            this.applicationAssignService.removeByAppCodeAndTenantCode(entity.getCode(), Me.tenantCode());
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    applicationContext.publishEvent(new ApplicationAssignCancelEvent(this, Me.tenantCode(), entity.getCode()));
                    applicationContext.publishEvent(new ApplicationMenuRemoveEvent(this, Me.tenantCode(), entity.getCode()));
                }
            });
        } else {
            entity.setStatus(ApplicationStatusEnum.UNPUBLISHED.name());
            this.applicationAssignService.removeByAppCode(entity.getCode());
            this.service.updateById(entity);
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    applicationContext.publishEvent(new ApplicationAssignCancelEvent(this, entity.getCode()));
                    applicationContext.publishEvent(new ApplicationMenuRemoveEvent(this, Tools.Str.EMPTY, entity.getCode()));
                }
            });
        }
        return R.ok();
    }

    @Override
    public R<Object> enable(String id) {
        ApplicationDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(ApplicationExceptionInfo.APPLICATION_NOT_FOUND);
        }
        ApplicationAssignDO applicationAssignEntity = this.applicationAssignService.getByAppCodeAndTenantCode(entity.getCode(), Me.tenantCode());
        applicationAssignEntity.setEnabled(true);
        this.applicationAssignService.updateById(applicationAssignEntity);
        return R.ok();
    }

    @Override
    public R<Object> disable(String id) {
        ApplicationDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(ApplicationExceptionInfo.APPLICATION_NOT_FOUND);
        }
        ApplicationAssignDO applicationAssignEntity = this.applicationAssignService.getByAppCodeAndTenantCode(entity.getCode(), Me.tenantCode());
        applicationAssignEntity.setEnabled(false);
        this.applicationAssignService.updateById(applicationAssignEntity);
        return R.ok();
    }

    @EventListener(SystemInitEvent.class)
    public void systemInitEventListener() {
        LogHelper.info("SERVICE-APPLICATION", "SYSTEM-INIT", "Create tenant default application.");
        EmbeddedApplicationEnum[] embeddedApp = EmbeddedApplicationEnum.values();
        if (this.service.existByCodes(Tools.Array.convertList(embeddedApp, value -> value.code))) {
            return;
        }
        this.service.saveBatch(Tools.Array.convertList(embeddedApp, platformApp -> {
            ApplicationDO entity = new ApplicationDO();
            entity.setId(Tools.Id.uuid());
            entity.setCode(platformApp.code);
            entity.setIcon(platformApp.icon);
            entity.setIconBackground(platformApp.iconBackground);
            entity.setName(platformApp.description);
            entity.setStatus(ApplicationStatusEnum.PUBLISHED.name());
            entity.setRemark(platformApp.remark);
            entity.setType(platformApp.type.name());
            entity.setIndexPath(platformApp.indexPath);
            return entity;
        }));
    }

    private List<ApplicationDTO> modelConvert(List<ApplicationDO> entities, List<ApplicationAssignDO> applicationAssignEntities) {
        Map<String, Boolean> applicationEnableMap = Tools.Coll.convertMap(applicationAssignEntities, ApplicationAssignDO::getAppCode, ApplicationAssignDO::getEnabled);
        return Tools.Coll.convertList(entities, entity -> this.modelConvert(entity, applicationEnableMap.get(entity.getCode())));
    }

    private ApplicationDTO modelConvert(ApplicationDO entity, Boolean enabled) {
        ApplicationDTO application = Tools.Bean.copy(entity, ApplicationDTO.class);
        if (null != application.getStatus()) {
            application.setStatusName(application.getStatus().description);
        }
        if (null != application.getType()) {
            application.setTypeName(application.getType().description);
        }
        if (null != enabled) {
            application.setEnabled(enabled);
        }
        return application;
    }
}
